<?php

declare(strict_types=1);

namespace App\Pay\Service;

use App\Activity\Model\ActivityModel;
use App\Common\Constants\ErrorCode;
use App\Common\Constants\Stakeholder;
use App\Common\Service\BaseService;
use App\Order\Service\OrderGoodsService;
use App\Order\Service\OrderService;
use App\Pay\Event\KzPayPush;
use App\Pay\Event\Notify;
use App\Pay\Event\Paid;
use App\Pay\Model\NotifyLog;
use App\Pay\Model\PayLog;
use App\Resource\Model\GoodsListModel;
use App\Resource\Service\ShopService;
use App\Third\Service\Kz\KzMainService;
use App\Third\Service\Wx\WxPayService;
use App\User\Service\MemberService;
use Hyperf\Di\Annotation\Inject;

class PayService extends BaseService
{

    /**
     * @Inject()
     * @var MemberService
     */
    private $memberService;

    /**
     * @Inject()
     * @var WxPayService
     */
    private $wxPayService;

    /**
     * @Inject()
     * @var OrderService
     */
    private $orderService;

    /**
     * @Inject()
     * @var ShopService
     */
    private $shopService;

    /**
     * @Inject()
     * @var OrderGoodsService
     */
    private $orderGoodsService;

    /**
     * @Inject()
     * @var KzMainService
     */
    private $kzMainService;

    /**
     * 余额支付
     * @param array $params
     * @return array
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function balancePayment(array $params){
        $res = $this->orderIsExist($params);
        if ($res['code'] == 0){
            return $res;
        }
        $orderInfo = $res['data']->toArray();
        $memberInfo = $this->memberService->findFirst(['mid' => $orderInfo['mid']], ['nickname','phone','kz_cus_code'])->toArray();
        if (!$memberInfo || !$memberInfo['kz_cus_code']) {
            return [
                'code' => 0,
                'errorCode' => ErrorCode::SYSTEM_INVALID,
                'throw' => "余额支付异常--用户信息异常\n".__METHOD__ . ":line:".__LINE__."\n".
                    "订单编号[向下]\n{$params['order_no']}",
                'msg' => '用户不合法'
            ];
        }
        $resss = $this->checkCouponRepeatUse($orderInfo, $memberInfo['kz_cus_code']);
        if ($resss['code'] == 0){
            return $resss;
        }
        $orderInfo['use_coupon_total'] = $orderInfo['total_price']; // 使用优惠券之后的金额
        $orderInfo['total_price'] = bcadd((string)$orderInfo['total_price'], (string)$orderInfo['coupon_cut'], 2); // 实际总金额
        $coupon = json_decode($orderInfo['coupon_info'], true) ?? []; // 优惠券信息
        $shopInfo = $this->shopService->shopInfoByWhere(['shop_id' => $orderInfo['shop_id']], ['shop_no', 'shop_name']);

        $orderGoods = $this->orderGoodsService->getOrderGoodsList(['order_no' => $params['order_no']]);
        $ress = $this->nextDayGoodsWhetherLimit($orderInfo, $orderGoods);
        if ($ress['code'] == 0){
            return $ress;
        }
        $goods=[];
        $payInfo = [];
        foreach ($orderGoods as $k => $v) {
            $goods[$k]['fShelfNum'] = $v['kz_self_num'] ?? '';
            $goods[$k]['fDiscount'] = 0;
            $goods[$k]['goodsId'] = $v['kz_goods_id'] ?? $v['goods_id'];
            $goods[$k]['typeId'] = $v['kz_type_id'] ?? 0;
            $goods[$k]['goodsName'] = $v['goods_title'];
            $goods[$k]['goodsPrice'] = bcmul((string)$v['selling_price'],'100');
            $goods[$k]['goodsNum'] = $v['number'];
            $goods[$k]['allPrice'] = bcmul((string)$goods[$k]['goodsPrice'], (string)$v['number']);
        }
        if (!empty($coupon) && !is_null($coupon)){
            // 使用优惠券
            foreach (json_decode($coupon['coupon'], true) as $k => $v) {
                $payInfo[$k]['payType'] = Stakeholder::KZ_PAY_COUPON;
                $payInfo[$k]['fee'] = (int)$v['deductMoney'];
                $payInfo[$k]['ticketTypeCode'] = $v['ticket_code'];
            }
            array_unshift($payInfo, ['payType' => Stakeholder::KZ_PAY_BALANCE, 'fee' => bcmul((string)$orderInfo['use_coupon_total'],'100')]);
        }else{
            // 不使用优惠券
            if ($orderInfo['total_price'] > 0) {
                $payInfo[] = [
                    "payType" => 1,
                    "fee" => bcmul((string)$orderInfo['total_price'],'100')
                ];
            }
        }
        // 客至支付
        $kzPayRes = $this->kzMainService->pay($memberInfo, $orderInfo, $shopInfo, $coupon, $payInfo, $goods, 'balance');
        if ($kzPayRes['code'] == 1){
            $data = [
                'order_no' => $params['order_no'],
                'pay_type' => Stakeholder::PAYMENT_METHOD_BALANCE,
                'shop_member_system' => Stakeholder::PAY_SYSTEM_TYPE_KZ,
            ];
            // 状态/模板消息/清空购物车/销量库存
            $this->eventDispatcher->dispatch(new Notify($data));
            //推送外卖单
            $this->eventDispatcher->dispatch(new Paid($params['order_no']));

//            $this->updateOrderGoodsDiscountInfo($params['order_no'], $kzPayRes,$orderGoods);
            $this->updateOrderGoodsDiscountInfoSelf($orderInfo, $orderInfo['coupon_cut'] > 0, $orderGoods);

            return ['code' => 1, 'msg' => '订单支付成功', 'data' => []];
        }
        return $kzPayRes;
    }


    /**
     * 订单商品数据明细 更新分摊金额数据
     * 自算分摊
     * @param array $order
     * @param bool $is
     * @param array $orderGoods
     * @return array
     * @author liule
     * @since 20210103151700
     */
    public function updateOrderGoodsDiscountInfoSelf(array $order, bool $is, array $orderGoods){
        try {
            $share = [];
            if ($is) $share = $this->orderService->splitDiscount(
                array_sum([$order['goods_price'], $order['coupon_cut']]),
                $order['coupon_cut'],
                array_column($orderGoods, null, 'id')
            );
            foreach ($orderGoods as $k => $v) {
                if (!empty($share)){
                    $deductMoney = $share[$v['id']] ?? 0;
                    $allPrice = bcmul((string)$v['selling_price'], (string)$v['number'], 2) ?? 0;
                    $goodsDeductMoney = bcdiv((string)$deductMoney, (string)$v['number'], 3) ?? 0;
                    $goodsDisPrice = bcsub((string)$v['selling_price'], (string)$goodsDeductMoney, 2);
                    $disPrice = bcsub((string)$allPrice, (string)$deductMoney, 2);
                }else{
                    $deductMoney = 0;
                    $goodsDisPrice = $v['selling_price'];
                    $disPrice = bcmul((string)$v['selling_price'], (string)$v['number'], 2);
                }
                $this->orderGoodsService->update(['id' => $v['id']], [
                    'is_pay' => 1,
                    'deductMoney' => $deductMoney,
                    'goods_dis_price' => $goodsDisPrice,
                    'dis_price' => $disPrice
                ]);
            }
        }catch (\Exception $e) {
            $error = $e->getMessage();
            $line = $e->getLine();
            $map = ['order_no' => $order['order_no'], 'share' => $share];
            $this->writeLog('error', $map, ['error' => '更新商品优惠数据异常', 'errMsg' => $error], 'pay');
            return [
                'code' => 0,
                'errorCode' => ErrorCode::SYSTEM_INVALID,
                'throw' => "更新商品优惠数据异常\n".__METHOD__ . ":line:{$line}\n".
                    "订单编号[向下]\n{$order['order_no']}\n".
                    "异常信息[向下]\n{$error}\n".
                    "优惠数据[向下]\n".json_encode($share),
                'msg' => '数据异常'
            ];
        }
        return ['code' => 1, 'msg' => 'success'];
    }


    /**
     * 吾享支付
     * @param array $params
     * @return array|bool|mixed
     */
    public function weChatPayment(array $params){
        $res = $this->orderIsExist($params);
        if ($res['code'] == 0){
            return $res;
        }
        $orderInfo = $res['data']->toArray();
        $cusCode = $this->memberService->findValue(['mid' => $orderInfo['mid']], 'kz_cus_code');
        $resss = $this->checkCouponRepeatUse($orderInfo, $cusCode);
        if ($resss['code'] == 0){
            return $resss;
        }
        $orderGoods = $this->orderGoodsService->getOrderGoodsList(['order_no' => $params['order_no']]);
        $ress = $this->nextDayGoodsWhetherLimit($orderInfo, $orderGoods);
        if ($ress['code'] == 0){
            return $ress;
        }
        return $this->wxPayService->comPaySubmit($orderInfo['order_no'], $orderInfo['openid'], (float) $orderInfo['total_price']);

    }

    /**
     * 订单是否存在
     * @param array $params
     * @return array
     */
    public function orderIsExist(array $params){
        $info = $this->orderService->findFirst(['order_no' => $params['order_no']]);
        if ($info){
            if ($info->is_pay == Stakeholder::ORDER_PAID){
                return [
                    'code' => 0,
                    'errorCode' => ErrorCode::SYSTEM_INVALID,
                    'msg' => '订单已支付'
                ];
            }
            if ($info->order_type == Stakeholder::ORDER_TYPE_FREE and $info->is_pay == Stakeholder::ORDER_UNPAID){
                $end = ActivityModel::query()->where('activityID', $info->activity_id)->value('end_date');
                if (time() > strtotime($end)){
                    return [
                        'code' => 0,
                        'errorCode' => ErrorCode::SYSTEM_INVALID,
                        'msg' => '活动已下架'
                    ];
                }
            }
            return ['code' => 1, 'msg' => '订单信息正常', 'data' => $info];
        }
        return [
            'code' => 0,
            'errorCode' => ErrorCode::SYSTEM_INVALID,
            'throw' => "吾享支付异常--订单不存在\n".__METHOD__ . ":line:".__LINE__."\n".
                "订单编号[向下]\n{$params['order_no']}",
            'msg' => '订单信息不存在'
        ];
    }

    /**
     * 次日达商品每人每天是否超出限购检测
     * @param array $orderInfo
     * @param array $orderGoods
     * @return array
     */
    public function nextDayGoodsWhetherLimit(array $orderInfo, array $orderGoods){
        if (($orderInfo['order_source'] == Stakeholder::NEXT_DAY_ORDER) && ($orderInfo['order_type'] != Stakeholder::ORDER_TYPE_GROUP)){
            $goodsIdNumArr = array_column($orderGoods, 'number', 'goods_id');
            $goodsTitleArr = array_column($orderGoods, 'goods_title', 'goods_id');

            $goodsIdArr = array_keys($goodsIdNumArr);
            // 已支付的次日达商品数量
            $where = ['order.mid' => $orderInfo['mid']];
            $in = [
                ['og.goods_id', $goodsIdArr]
            ];
            $nextDayGoodsNumOfMid = $this->orderService->nextDayGoodsNumOfMid($where, $in);
            if (!empty($nextDayGoodsNumOfMid)) {
                $nextDayGoodsNumOfMidArr = array_column($nextDayGoodsNumOfMid, 'num', 'goods_id');

                // 每人每天限购数量
                $limitGoodsNumOfPerDay = GoodsListModel::query()
                    ->whereIn('goods_id', $goodsIdArr)
                    ->pluck('limite_num_per_day', 'goods_id')->toArray();

                foreach ($goodsIdNumArr as $goods_id => $num) {
                    if ($num + $nextDayGoodsNumOfMidArr[$goods_id] > $limitGoodsNumOfPerDay[$goods_id]) {
                        return [
                            'code' => 0,
                            'errorCode' => ErrorCode::CRD_GOODS_BEYOND_LIMIT_NUM,
                            'throw' => "",
                            'msg' => $goodsTitleArr[$goods_id] . '每人每天限购数量' . $limitGoodsNumOfPerDay[$goods_id]
                        ];
                    }
                }
            }
        }
        return ['code' => 1, 'msg' => '限购信息正常', 'data' => []];
    }

    /**
     * 检测优惠券是否重读使用
     * @param array $orderInfo
     * @param string $cusCode
     * @return array
     */
    public function checkCouponRepeatUse(array $orderInfo, string $cusCode){
        if ($orderInfo['coupon_status'] && !empty($orderInfo['coupon_info'])){
            $subcode = json_decode(json_decode($orderInfo['coupon_info'])->coupon, true)[0]['subcode'];
            // 同一种优惠券有多张，券码是一样的
//            $where = [
//                'mid' => $orderInfo['mid'],
//                'is_pay' => Stakeholder::ORDER_PAID,
//                'coupon_status' => Stakeholder::ORDER_IS_COUPON_YES,
//            ];
//            $res = $this->orderService->checkCouponIsUse($where, $ticketCode);
            $res = $this->kzMainService->memberCouponsByCusInfo($cusCode, 0, 0, 0); // 未使用优惠券集合
            if ($res['code']){
                $canUseTicketCode = array_column($res['data'], 'subcode');
                $is = array_key_exists($subcode, array_flip($canUseTicketCode));
                if (!$is) return ['code' => 0, 'errorCode' => ErrorCode::SYSTEM_INVALID, 'throw' => '', 'msg' => '订单优惠券已使用'];
            }
        }
        return ['code' => 1, 'msg' => '优惠券信息正常', 'data' => []];
    }

    /**
     * 签名
     * @param array $params
     * @return string
     */
    public function getSignContent(array $params)
    {
        $priKey = strtolower(md5($params['storeid']));
        $map['orderid'] = $params['orderid'];
        $map['ordno'] = $params['ordno'];
        $map['paytypeid'] = $params['paytypeid'];
        $map['storeid'] = $params['storeid'];
        $map['tradeState'] = $params['tradeState'];
        $query = http_build_query($map);
        return strtolower(md5($query . $priKey));
    }

    /**
     * 验签
     * @param array $params
     * @return array
     */
    public function checkSignature(array $params){
        $signature = $this->getSignContent($params);
        $this->writeLog('info', $params, ['info' => '吾享支付回调参数', 'signature' => $signature ]);
        if ($signature === $params['sign']){
            // 改变订单状态
            $data = [
                'order_no' => $params['orderid'],
                'pay_type' => Stakeholder::PAYMENT_METHOD_WECHAT,
                'shop_member_system' => Stakeholder::PAY_SYSTEM_TYPE_WX,
            ];
            $this->eventDispatcher->dispatch(new Notify($data));
            //推送外卖单(避免重复推送)
            $takeaway = $this->orderService->findFirst(['order_no' => $params['orderid']], ['deNo', 'deId']);
            if (empty($takeaway->deNo) || empty($takeaway->deId)){
                $this->eventDispatcher->dispatch(new Paid($params['orderid']));
                // 传入客至支付/核销优惠券
                $this->eventDispatcher->dispatch(new KzPayPush($params['orderid']));
            }

            return ['code' => 1,'errorText' => '成功','returnCode' => 1,'errorCode' => 200];
        }else{
            // 验签失败，写入日志
            $response = [
                'exception' => '吾享支付回调验签失败',
                'signature' => $signature
            ];
            $this->writeLog('exception', $params, $response);
            return [
                'code' => 0,
                'returnCode' => 0,
                'errorCode' => ErrorCode::SYSTEM_INVALID,
                'errorText' => '验签失败',
                'throw' => "支付回调验签失败异常\n".__METHOD__ . ":line:".__LINE__."\n".
                    "订单编号[向下]\n{$params['orderid']}\n".
                    "生成签名[向下]\n{$signature}\n".
                    "回调参数[向下]\n".json_encode($params),
            ];
        }
    }

    /**
     * 支付成功发送服务通知
     * @param object $orderInfo
     * @param $orderNo
     * @return mixed
     */
    public function sendPaySuccessMsg(object $orderInfo){
        $nickname = $this->memberService->findValue(['mid' => $orderInfo->mid], 'nickname');
        $template_id = '9csmvch1y23-2FVfvla2vayMCDqkpgkFpzqjMWuPF98';
        $page = "pages/order-list/order-list";
        $map = [
            "character_string3" => [
                "value" => $orderInfo->order_no
            ],
            "amount4" => [
                "value" => $orderInfo->total_price
            ],
            "date5" => [
                "value" => $orderInfo->create_at
            ],
            "name1" => [
                "value" => $nickname
            ],
        ];
        return $this->sendMiniAppSubscribeMsg($orderInfo->openid, $template_id, $page, $map);
    }

    /**
     * 支付和回调日志入库
     * @param string $event
     * @param array $params
     * @param array $response
     * @param string|null $type
     * @return \Hyperf\Database\Model\Builder|\Hyperf\Database\Model\Model
     */
    public function writeLog(string $event, array $params , array $response, string $type = null){
        $data = [
            'event' => $event,
            'params' => json_encode($params),
            'response' => json_encode($response, JSON_UNESCAPED_UNICODE),
        ];
        if ($type == 'pay'){
            return PayLog::query()->create($data);
        }
        return NotifyLog::query()->create($data);
    }

}
